#include "stdafx.h"
#include "HXDirector.h"
#include "HXTalk.h"
#include "HXLink.h"
#include "HXWindow.h"

#include <fstream>
#include <time.h>

#pragma warning(disable : 4996)

//=============================================================================

HXTalk g_talk(&g_alive);			// text processing and speech output

//=============================================================================

//! Constructor
HXTalk::HXTalk(HXAliveProcessor* pAlive)
: m_pSpeechTTS(NULL),
  m_pAlive(pAlive),
  m_pbThreadExit(NULL),
  m_responsePosition(-1),
  m_responseDelay(0.0),
  m_bBreakOnStop(false),
  m_bReplaceSubtitle(false)
{
}

//! Set the speech engine to output processed text to.
void HXTalk::SetSpeechClient(HXSpeech* speechTTS, bool* pbThreadExit)
{
	m_pSpeechTTS = speechTTS;
	m_pbThreadExit = pbThreadExit;
}

//! Process and speak a text.
void HXTalk::Speak(const wchar_t* text)
{
	g_link.SetIsTalking(HXSY_ACTIVE);
	wcscpy_s(m_response, MAX_TEXT_LENGTH, text);
	m_responsePosition = 0;
	m_responseDelay = 0.0;
	m_pauseStartTime = clock();
	HXParseTagType lasttag = HXPTT_NONE;
	//if (m_pAlive) m_pAlive->Clear(HXATM_ACTIVE);
	bool bFirstSegment = true;
	while (m_responsePosition >= 0)
	{
		double time = static_cast<double>(clock() - m_pauseStartTime) / 1000.0;
		if (m_pbThreadExit && *m_pbThreadExit)
		{
			if (m_pAlive) m_pAlive->Clear(HXATF_ACTIVE);
			break;
		}
		else
		if ((time >= m_responseDelay) && (bFirstSegment || (!m_pAlive || (m_pAlive && !m_pAlive->Count(HXATF_ACTIVE)))))
		{
			if (bFirstSegment && m_pAlive) m_pAlive->SlowClear(HXATF_ACTIVE);
			if (!(lasttag & HXPTT_EMOTE))
			{
				m_responseDelay = 0.0;
				m_pauseStartTime = clock();
			}
			wchar_t buf[MAX_TEXT_LENGTH];
			lasttag = Parse(buf);
			SpeakDirect(buf);
			if (m_pAlive) m_pAlive->Trig(MAX_WORDINDEX, true);
			bFirstSegment = false;
		}
	}
	//g_link.SetIsTalking(HXSY_NONE);
}

//! Speak a text without processing.
void HXTalk::SpeakDirect(const wchar_t* text)
{
	if (m_pAlive) m_pAlive->Trig(0, HXATF_COMMAND); // pre-trigger any in-line commands before speech
	while (g_director.QueueSize()) { Sleep(1); } // delay speech until all commands are processed
	if (m_pSpeechTTS && m_pbThreadExit && text)
	{
		size_t i = 0;
		while (text[i])
		{
			if ((text[i] == L' ') ||
				(text[i] == L'\t') ||
				(text[i] == L'\r') ||
				(text[i] == L'\n'))
			{
				i++;
			}
			else
			{
				break;
			}
		} 
		if (text[i])
		{
			g_logger.Log(text+i, L'O');
			int prevState = g_link.GetIsTalking();
			g_link.SetIsTalking(HXSY_SPEAKING);
			// eliminate angled brackets for SpokenText/subtitles...
			wchar_t buffer[MAX_TEXT_LENGTH];
			bool in_angle = false;
			size_t k = 0;
			for (size_t j = i; j <= wcslen(text); j++)
			{
				if (text[j] == L'<')
				{
					in_angle = true;
				}
				else if (text[j] == L'>')
				{
					in_angle = false;
				}
				else if (!in_angle)
				{
					buffer[k++] = text[j];
				}
			}
			// ...
			g_link.SetSpokenText(WStringToString(wstring(buffer)).c_str());
			if (m_bReplaceSubtitle)
			{
				g_subtitle.SetSubtitleSpokenRatio(m_subtitleString, wstring(buffer));
				g_subtitle.SetSpoken(WStringToString(m_subtitleString));
			}
			else
			{
				g_subtitle.SetSpoken(WStringToString(wstring(buffer)));
			}
			m_pSpeechTTS->Speak(text, m_pbThreadExit);
			g_link.SetIsTalking(prevState);
		}
	}
	// clear alternative subtitle
	if (m_bReplaceSubtitle)
	{
		m_bReplaceSubtitle = false;
		m_subtitleString.clear();
		g_subtitle.SetSubtitleSpokenRatio(1.0f);
	}
}

//! Clears toggled expressions - must be called while expression definitions are still loaded!
void HXTalk::ClearExpressionToggles(bool trigRevert, float delay, unsigned int trigIndex)
{
	map<wstring, vector<HXExpression>>::iterator exprIt = g_config.expressionMap.begin();
	for (; exprIt != g_config.expressionMap.end(); ++exprIt)
	{
		vector<HXExpression>::iterator choiceIt = exprIt->second.begin();
		for (; choiceIt != exprIt->second.end(); ++choiceIt)
		{
			if (choiceIt->toggle)
			{
				for (size_t i = 0; i < choiceIt->shapes.size(); i++)
				{
					HXAliveMotion* pMotion = new HXAliveMotion();
					pMotion->SetMorph(choiceIt->shapes[i].name, -choiceIt->toggle*choiceIt->shapes[i].amplitude);
					pMotion->Set(choiceIt->shapes[i].duration/2, delay+choiceIt->shapes[i].offset, choiceIt->shapes[i].rampFunc);
					if (trigRevert)
						pMotion->TriggeredStart(trigIndex);
					else
						pMotion->Start();
					m_pAlive->Add(pMotion);
				}
				for (size_t i = 0; i < choiceIt->movements.size(); i++)
				{
					HXAliveMotion* pMotion = new HXAliveMotion();
					if (choiceIt->movements[i].mode == HXMOV_ROTATION)
						pMotion->SetRotation(-choiceIt->toggle*choiceIt->movements[i].x, -choiceIt->toggle*choiceIt->movements[i].y, -choiceIt->toggle*choiceIt->movements[i].z);
					else if (choiceIt->movements[i].mode == HXMOV_TRANSLATION)
						pMotion->SetTranslation(-choiceIt->toggle*choiceIt->movements[i].x, -choiceIt->toggle*choiceIt->movements[i].y, -choiceIt->toggle*choiceIt->movements[i].z);
					else if (choiceIt->movements[i].mode == HXMOV_SCALE)
						pMotion->SetScale(-choiceIt->toggle*choiceIt->movements[i].x, -choiceIt->toggle*choiceIt->movements[i].y, -choiceIt->toggle*choiceIt->movements[i].z);
					pMotion->Set(choiceIt->movements[i].duration/2, delay+choiceIt->movements[i].offset, choiceIt->movements[i].rampFunc);
					pMotion->SetGroup(choiceIt->movements[i].group);
					if (trigRevert)
						pMotion->TriggeredStart(trigIndex);
					else
						pMotion->Start();
					m_pAlive->Add(pMotion);
				}
				choiceIt->toggle = 0.0f;
			}
		}
	}
}

//! Process and remove tags from text.
HXParseTagType HXTalk::Parse(wchar_t* filtered) 
{
	int lastprinted = m_responsePosition;
	HXParseTagType parsedtag = HXPTT_NONE;
	int f = 0;
	int inrecursion = 0;
	int inrecursion_mem = 0;
	wchar_t tokenbuf[MAX_TEXT_LENGTH];
	bool ignore_stops = false;
	bool ignore_words = false;
	m_wordIndex = 0;
	for (int& i = m_responsePosition; i < (int)wcslen(m_response); i++)
	{
		if (!inrecursion)
		{
			if (m_response[i] == L'<')
			{
				ignore_words = true;
			}
			else
			if (m_response[i] == L'>')
			{
				ignore_words = false;
			}
			if (!ignore_words && (i > 0)
				&& ((m_response[i] == L' ') || (m_response[i] == L'\t') || (m_response[i] == L'\r') || (m_response[i] == L'\n') || (m_response[i] == L'{')))
			{
				switch (m_response[i-1])
				{
					case L' ':
					case L'\t':
					case L'\r':
					case L'\n':
					case L'}':
					case L'>':
						break;
					default:
						m_wordIndex++;
				}
			}
		}
		if (m_bBreakOnStop && !ignore_stops && ((m_response[i] == L'.') || (m_response[i] == L';') || (m_response[i] == L':') || (m_response[i] == L'!') || (m_response[i] == L'?')))
		{
			while (lastprinted <= i)
			{
				filtered[f++] = m_response[lastprinted++];
			}
			parsedtag = HXPTT_BREAK;
			i++; // move beyond the stop
		}
		else
		if (m_response[i] == L'{') // skip braces within braces
		{
			inrecursion++;
			inrecursion_mem++;
			ignore_stops = true;
		}
		else
		if (m_response[i] == L'}') // found a tag!
		{
			inrecursion--;
			ignore_stops = false;
			if (inrecursion < 0) // excess }
			{
				inrecursion = 0;
				lastprinted = i+1;
			}
			else 
			if (inrecursion == 0)
			{
				for (int j = i-1; j >= lastprinted; j--)
				{
					if ((m_response[j] == L'{') && (!(--inrecursion_mem)))
					{
						while (lastprinted < j)
						{
							filtered[f++] = m_response[lastprinted++];
						}
						int k = 0;
						while (k < i-j-1)
						{
							//tokenbuf[k] = static_cast<wchar_t>(towlower(m_response[k+j+1]));
							tokenbuf[k] = static_cast<wchar_t>(m_response[k+j+1]);
							k++;
						}
						tokenbuf[k] = L'\0';
						parsedtag = ParseTag(tokenbuf);
						if (parsedtag & HXPTT_REINSERT)
						{
							k = 0;
							while (m_replace[k])
							{
								filtered[f++] = m_replace[k++];
							}
						}
						break;
					}
				}
				lastprinted = i+1;
			}
		}
		if (parsedtag & HXPTT_BREAK) break;
	}
	if (!(parsedtag & HXPTT_BREAK))
	{
		for (int l = lastprinted; l < (int)wcslen(m_response); l++)
		{
			filtered[f++] = m_response[l];
		}
		m_responsePosition = -1;
	}
	filtered[f] = L'\0';
	for (int m = 0; m < (int)wcslen(filtered); m++)
	{
		switch (filtered[m])
		{
			case L'\n':
			case L'\r':
				filtered[m] = L' ';
				break;
			default:
				break;
		}
	}
	if (!parsedtag && wcslen(filtered))
		g_window.ShowStatus(L"Parse output: %s\n", filtered);
	return parsedtag;
}

//! Process a tag.
HXParseTagType HXTalk::ParseTag(const wchar_t *text, bool staticUse)
{
	wchar_t *taglet;
	wchar_t *next_token;

	wchar_t textLowCopy[MAX_TEXT_LENGTH];
	//wcscpy_s(textLowCopy, MAX_TEXT_LENGTH, text);
	size_t textLength = wcslen(text);
	for (size_t i = 0; i <= textLength; i++)
	{
		textLowCopy[i] = towlower(text[i]);
	}
	
	if (taglet = wcstok_s(textLowCopy, L" ,\t\n\r", &next_token))
	{
		if ((wcscmp(L"em", taglet) == 0) || (wcscmp(L"emote", taglet) == 0))
		{
			float emoDelay = 0.0f;
			size_t added = 0;
			list<HXAliveMotion*> commandList;
			while (taglet = wcstok_s(NULL, L" ,\t\n\r", &next_token))
			{
				int taglen = wcslen(taglet);
				if ((taglen == 1) && (taglet[0] == L'|'))
				{
					if (!staticUse)
						m_responseDelay = (emoDelay > m_responseDelay) ? emoDelay : m_responseDelay;
					emoDelay = 0.0f;
					added = 0;
				}
				else
				if ((taglen == 1) && (taglet[0] == L'='))
				{
					ClearExpressionToggles(true, emoDelay, staticUse ? 0 : m_wordIndex);
				}
				else if (taglen)
				{
					double duration;
					if (duration = wcstod(taglet, NULL))
					{
						if (commandList.size())
						{
							list<HXAliveMotion*>::reverse_iterator alit = commandList.rbegin();
							for (; (alit != commandList.rend()) && added; ++alit, added--)
							{
								(*alit)->Extend(0.0f, static_cast<float>(duration));
							}
							emoDelay += static_cast<float>(duration);
						}
					}
					else
					{
						map<wstring, vector<HXExpression>>::iterator findIt;
						if ((taglet[0] == L'+') || (taglet[0] == L'-'))
						{
							findIt = g_config.expressionMap.find(taglet+1);
						}
						else
						{
							findIt = g_config.expressionMap.find(taglet);
						}
						if (findIt != g_config.expressionMap.end())
						{
							unsigned int choice = 0;
							if (findIt->second.size() > 1)
							{
								rand_s(&choice);
								choice %= findIt->second.size();
							}
							int divisor = 2;
							if (!findIt->second[choice].bounce) {
								divisor = 1;
							}
							if ((taglet[0] == L'+') || (!findIt->second[choice].bounce && (taglet[0] != L'-')))
							{
								float maxDuration = 0;
								for (size_t i = 0; i < findIt->second[choice].shapes.size(); i++)
								{
									HXAliveMotion* pMotionAscent = new HXAliveMotion();	
									pMotionAscent->SetMorph(findIt->second[choice].shapes[i].name, findIt->second[choice].shapes[i].amplitude);
									pMotionAscent->Set(findIt->second[choice].shapes[i].duration/divisor, emoDelay+findIt->second[choice].shapes[i].offset/divisor, findIt->second[choice].shapes[i].rampFunc);
									if ((findIt->second[choice].shapes[i].duration/divisor+findIt->second[choice].shapes[i].offset/divisor) > maxDuration)
										maxDuration = findIt->second[choice].shapes[i].duration/divisor+findIt->second[choice].shapes[i].offset/divisor;
									commandList.push_front(pMotionAscent);
								}
								for (size_t i = 0; i < findIt->second[choice].movements.size(); i++)
								{
									HXAliveMotion* pMotionAscent = new HXAliveMotion();
									if (findIt->second[choice].movements[i].mode == HXMOV_ROTATION)
										pMotionAscent->SetRotation(findIt->second[choice].movements[i].x, findIt->second[choice].movements[i].y, findIt->second[choice].movements[i].z);
									else if (findIt->second[choice].movements[i].mode == HXMOV_TRANSLATION)
										pMotionAscent->SetTranslation(findIt->second[choice].movements[i].x, findIt->second[choice].movements[i].y, findIt->second[choice].movements[i].z);
									else if (findIt->second[choice].movements[i].mode == HXMOV_SCALE)
										pMotionAscent->SetScale(findIt->second[choice].movements[i].x, findIt->second[choice].movements[i].y, findIt->second[choice].movements[i].z);
									pMotionAscent->Set(findIt->second[choice].movements[i].duration/divisor, emoDelay+findIt->second[choice].movements[i].offset/divisor, findIt->second[choice].movements[i].rampFunc);
									pMotionAscent->SetGroup(findIt->second[choice].movements[i].group);
									if ((findIt->second[choice].movements[i].duration/divisor+findIt->second[choice].movements[i].offset/divisor) > maxDuration)
										maxDuration = findIt->second[choice].movements[i].duration/divisor+findIt->second[choice].movements[i].offset/divisor;
									commandList.push_front(pMotionAscent);
								}
								emoDelay += maxDuration;
								if (findIt->second[choice].bounce) {
									findIt->second[choice].toggle += 1.0f;
								}
							}
							else
							if (taglet[0] == L'-')
							{
								float maxDuration = 0;
								for (size_t i = 0; i < findIt->second[choice].shapes.size(); i++)
								{
									HXAliveMotion* pMotionDescent = new HXAliveMotion();	
									pMotionDescent->SetMorph(findIt->second[choice].shapes[i].name, -findIt->second[choice].shapes[i].amplitude);
									pMotionDescent->Set(findIt->second[choice].shapes[i].duration/divisor, emoDelay+findIt->second[choice].shapes[i].offset/divisor, findIt->second[choice].shapes[i].rampFunc);
									if ((findIt->second[choice].shapes[i].duration/divisor+findIt->second[choice].shapes[i].offset/divisor) > maxDuration)
										maxDuration = findIt->second[choice].shapes[i].duration/divisor+findIt->second[choice].shapes[i].offset/divisor;
									commandList.push_back(pMotionDescent);
									added++;
								}
								for (size_t i = 0; i < findIt->second[choice].movements.size(); i++)
								{
									HXAliveMotion* pMotionDescent = new HXAliveMotion();
									if (findIt->second[choice].movements[i].mode == HXMOV_ROTATION)
										pMotionDescent->SetRotation(-findIt->second[choice].movements[i].x, -findIt->second[choice].movements[i].y, -findIt->second[choice].movements[i].z);
									else if (findIt->second[choice].movements[i].mode == HXMOV_TRANSLATION)
										pMotionDescent->SetTranslation(-findIt->second[choice].movements[i].x, -findIt->second[choice].movements[i].y, -findIt->second[choice].movements[i].z);
									else if (findIt->second[choice].movements[i].mode == HXMOV_SCALE)
										pMotionDescent->SetScale(-findIt->second[choice].movements[i].x, -findIt->second[choice].movements[i].y, -findIt->second[choice].movements[i].z);
									pMotionDescent->Set(findIt->second[choice].movements[i].duration/divisor, emoDelay+findIt->second[choice].movements[i].offset/divisor, findIt->second[choice].movements[i].rampFunc);
									pMotionDescent->SetGroup(findIt->second[choice].movements[i].group);
									if ((findIt->second[choice].movements[i].duration/divisor+findIt->second[choice].movements[i].offset/divisor) > maxDuration)
										maxDuration = findIt->second[choice].movements[i].duration/divisor+findIt->second[choice].movements[i].offset/divisor;
									commandList.push_back(pMotionDescent);
									added++;
								}
								emoDelay += maxDuration;
								if (findIt->second[choice].bounce) {
									findIt->second[choice].toggle -= 1.0f;
								}
							}
							else
							{
								float maxDuration = 0;
								for (size_t i = 0; i < findIt->second[choice].shapes.size(); i++)
								{
									HXAliveMotion* pMotionAscent = new HXAliveMotion();	
									HXAliveMotion* pMotionDescent = new HXAliveMotion();	
									pMotionAscent->SetMorph(findIt->second[choice].shapes[i].name, findIt->second[choice].shapes[i].amplitude);
									pMotionAscent->Set(findIt->second[choice].shapes[i].duration/2, emoDelay+findIt->second[choice].shapes[i].offset, findIt->second[choice].shapes[i].rampFunc);
									pMotionDescent->SetMorph(findIt->second[choice].shapes[i].name, -findIt->second[choice].shapes[i].amplitude);
									pMotionDescent->Set(findIt->second[choice].shapes[i].duration/2, emoDelay+findIt->second[choice].shapes[i].offset+findIt->second[choice].shapes[i].duration/2, findIt->second[choice].shapes[i].rampFunc);
									if ((findIt->second[choice].shapes[i].duration+findIt->second[choice].shapes[i].offset) > maxDuration)
										maxDuration = findIt->second[choice].shapes[i].duration+findIt->second[choice].shapes[i].offset;
									commandList.push_front(pMotionAscent);
									commandList.push_back(pMotionDescent);
									added++;
								}
								for (size_t i = 0; i < findIt->second[choice].movements.size(); i++)
								{
									HXAliveMotion* pMotionAscent = new HXAliveMotion();
									HXAliveMotion* pMotionDescent = new HXAliveMotion();
									if (findIt->second[choice].movements[i].mode == HXMOV_ROTATION)
									{
										pMotionAscent->SetRotation(findIt->second[choice].movements[i].x, findIt->second[choice].movements[i].y, findIt->second[choice].movements[i].z);
										pMotionDescent->SetRotation(-findIt->second[choice].movements[i].x, -findIt->second[choice].movements[i].y, -findIt->second[choice].movements[i].z);
									}
									else if (findIt->second[choice].movements[i].mode == HXMOV_TRANSLATION)
									{
										pMotionAscent->SetTranslation(findIt->second[choice].movements[i].x, findIt->second[choice].movements[i].y, findIt->second[choice].movements[i].z);
										pMotionDescent->SetTranslation(-findIt->second[choice].movements[i].x, -findIt->second[choice].movements[i].y, -findIt->second[choice].movements[i].z);
									}
									else if (findIt->second[choice].movements[i].mode == HXMOV_SCALE)
									{
										pMotionAscent->SetScale(findIt->second[choice].movements[i].x, findIt->second[choice].movements[i].y, findIt->second[choice].movements[i].z);
										pMotionDescent->SetScale(-findIt->second[choice].movements[i].x, -findIt->second[choice].movements[i].y, -findIt->second[choice].movements[i].z);
									}
									pMotionAscent->Set(findIt->second[choice].movements[i].duration/2, emoDelay+findIt->second[choice].movements[i].offset, findIt->second[choice].movements[i].rampFunc);
									pMotionDescent->Set(findIt->second[choice].movements[i].duration/2, emoDelay+findIt->second[choice].movements[i].offset+findIt->second[choice].movements[i].duration/2, findIt->second[choice].movements[i].rampFunc);
									pMotionAscent->SetGroup(findIt->second[choice].movements[i].group);
									pMotionDescent->SetGroup(findIt->second[choice].movements[i].group);
									if ((findIt->second[choice].movements[i].duration+findIt->second[choice].movements[i].offset) > maxDuration)
										maxDuration = findIt->second[choice].movements[i].duration+findIt->second[choice].movements[i].offset;
									commandList.push_front(pMotionAscent);
									commandList.push_back(pMotionDescent);
									added++;
								}
								emoDelay += maxDuration;
							}
						}
					}
				}
			}
			if (!staticUse)
				m_responseDelay = (emoDelay > m_responseDelay) ? emoDelay : m_responseDelay;
			bool flip = true;
			list<HXAliveMotion*>::iterator acit = commandList.begin();
			for (; acit != commandList.end(); ++acit)
			{
				if (flip)
				{
					flip = !flip;
				}
				if (staticUse)
				{
					(*acit)->Start();
				}
				else
				{
					(*acit)->TriggeredStart(m_wordIndex);
				}
				m_pAlive->Add(*acit);
			}
			g_window.ShowStatus(L"Parsing EMOTE tag (after word %d), with %d expression segments", m_wordIndex, commandList.size());
			return HXPTT_EMOTE;
		}
		else
		if ((wcscmp(L"br", taglet) == 0) || (wcscmp(L"break", taglet) == 0))
		{
			double wait = 0;
			if (taglet = wcstok_s(NULL, L" ,\t\n\r", &next_token))
			{
				wait = wcstod(taglet, NULL);
				m_responseDelay += wait;
			}
			g_window.ShowStatus(L"Parsing BREAK tag with duration: %g (total wait is %gs)", wait, m_responseDelay);
			return HXPTT_BREAK; // break on tag
		}
		else
		if (wcscmp(L"sub", taglet) == 0)
		{
			wstring subtitle = wstring(text+(next_token-textLowCopy));
			if (m_bReplaceSubtitle)
			{
				m_subtitleString.push_back(L' ');
			}
			else
			{
				m_bReplaceSubtitle = true;
			}
			m_subtitleString.append(subtitle);
			g_window.ShowStatus(L"Parsing SUB tag with string: %s", subtitle.c_str());
			return HXPTT_SUBTITLE;
		}
		else
		if (wcscmp(L"pause", taglet) == 0)
		{
			double wait = 1;
			if (taglet = wcstok_s(NULL, L" ,\t\n\r", &next_token))
			{
				wait = wcstod(taglet, NULL);
			}
			swprintf_s(m_replace, MAX_TEXT_LENGTH, L" <bookmark mark=\"HXPAUSE\"/> <silence msec=\"%d\"/> ", static_cast<int>(wait*1000));
			m_wordIndex++;
			return HXPTT_REINSERT;
		}
		else
		{
			HXAliveCommand* pCommand = new HXAliveCommand(text);
			pCommand->TriggeredStart(m_wordIndex);
			m_pAlive->Add(pCommand);
			return HXPTT_SYSTEM;
		}
	}
	return HXPTT_NONE; // no break on tag
}

// -----------------------------------------------------------------------------